package com.oocl.euc.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.oocl.csc.frm.sso.exceptions.SSOException;
import com.oocl.csc.frm.sso.util.SSOHelperUtils;
import com.oocl.euc.authentication.core.RoleInfo;
import com.oocl.euc.authentication.core.SSOAuthority;
import com.oocl.euc.authentication.core.SSOUserContext;
import com.oocl.euc.authentication.core.UserInfo;
import com.oocl.euc.authentication.tool.CookieHelper;
import com.oocl.euc.authentication.tool.SsoTokenHelper;
import com.oocl.euc.common.constant.ApplicationConstant;
import com.oocl.euc.common.utils.UserContextHolder;
import com.oocl.euc.framework.authentication.common.AccessJwtToken;
import com.oocl.euc.framework.authentication.common.JwtToken;
import com.oocl.euc.framework.authentication.exception.InvalidJwtTokenException;
import com.oocl.euc.framework.authentication.exception.JwtTokenExpiredException;
import com.oocl.euc.framework.authentication.jwt.JwtSettings;
import com.oocl.euc.framework.authentication.jwt.TokenExtractor;
import com.oocl.euc.framework.response.Result;
import io.jsonwebtoken.Claims;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class JwtAuthenticationFilter extends GenericFilterBean {
    private static Logger logger= LoggerFactory.getLogger(JwtAuthenticationFilter.class);
    private TokenExtractor jwtTokenExtractor;
    private SsoTokenHelper tokenHelper;
    private JwtSettings jwtSettings;
    private CookieHelper cookieHelper;
    private ObjectMapper objectMapper;

    public JwtAuthenticationFilter(TokenExtractor jwtTokenExtractor, SsoTokenHelper tokenHelper, JwtSettings jwtSettings,
                           CookieHelper cookieHelper, ObjectMapper objectMapper) {
        super();
        this.jwtTokenExtractor = jwtTokenExtractor;
        this.tokenHelper = tokenHelper;
        this.jwtSettings = jwtSettings;
        this.cookieHelper = cookieHelper;
        this.objectMapper = objectMapper;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        logger.info("Begin validation JWT token for " + req.getRequestURI());
        String tokenPayload = req.getHeader(ApplicationConstant.JWT_TOKEN_HEADER);
        try {
            AccessJwtToken accessJwtToken = new AccessJwtToken(this.jwtTokenExtractor.extract(tokenPayload));
            Claims claims = tokenHelper.parseClaims(jwtSettings.getTokenSigningKey(), accessJwtToken.getRawToken()).getBody();
            String openToken = claims.get("openToken", String.class);
            String info = objectMapper.writeValueAsString(claims.get("userInfo"));
            UserInfo userInfo = objectMapper.readValue(info, UserInfo.class);
            List<String> roles = claims.get("roles", List.class);
            List<SSOAuthority> roleInfos = new ArrayList<>();
            roles.stream().map(role -> {
                RoleInfo roleInfo = new RoleInfo();
                roleInfo.setAuthority(role);
                return roleInfos.add(roleInfo);
            }).collect(Collectors.toList());

            if(StringUtils.isEmpty(openToken)){
                throw new SSOException("No openToken found!");
            }
            String domainId = SSOHelperUtils.getTokenSubject(openToken);
            boolean valid = false;
            if(null == domainId){
                valid = SSOHelperUtils.doCheckTokenFromCookie(req, resp, ApplicationConstant.PCRS_SERVICE_ACCOUNT);
                if(!valid){
                    throw new SSOException("OpenToken expired");
                }
            }
            SSOUserContext userContext = new SSOUserContext();
            userContext.setRoles(roleInfos);
            userContext.setUserInfo(userInfo);
            UserContextHolder.setUserContext(userContext);

            if(valid) {
                Cookie jwtTokenCookie = cookieHelper.getCookie(req, ApplicationConstant.JWT_ACCESS_TOKEN);
                JwtToken accessToken = tokenHelper.createAccessTokenWithOpenToken(userContext, (String) req.getAttribute(ApplicationConstant.OPENTOKEN_COOKIE));
                cookieHelper.refreshTokenCookieForOOCL(resp, jwtTokenCookie, accessToken);
                logger.info("Create or refresh JWT token!");
            }
            chain.doFilter(req, resp);
            logger.info("Validation JWT token successfully for " + req.getRequestURI());
        } catch (SSOException | JwtTokenExpiredException | InvalidJwtTokenException e) {
            logger.error(JwtAuthenticationFilter.class.getSimpleName() + " JWT token validation failed...", e);
            resp.setStatus(HttpStatus.UNAUTHORIZED.value());
            new ObjectMapper().writeValue(resp.getWriter(), new Result("INVALID_TOKEN", e.getMessage(), null));
        }
    }
}
